package com.aceitunaproject.client;

import com.aceitunaproject.client.commands.EnumParameterType;
import com.aceitunaproject.client.commands.Order;

import com.aceitunaproject.client.commands.implementation.BuildInstallation;
import com.aceitunaproject.client.commands.implementation.BuildSettlement;
import com.aceitunaproject.client.commands.implementation.MoveUnit;
import com.aceitunaproject.client.commands.implementation.RecruitUnit;
import com.aceitunaproject.client.commands.implementation.parameters.BasicParameter;
import com.aceitunaproject.client.commands.implementation.parameters.InstallationParameter;
import com.aceitunaproject.client.commands.implementation.parameters.SettlementParameter;
import com.aceitunaproject.client.commands.implementation.parameters.UnitParameter;

import com.aceitunaproject.client.mapscreen.MapPane;
import com.aceitunaproject.clientinterface.*;
import com.aceitunaproject.clientinterface.eventadapters.*;
import com.aceitunaproject.clientinterface.eventregistering.*;
import com.aceitunaproject.mapdisplay.*;
import com.aceitunaproject.mapdisplay.model.Thing;
import com.aceitunaproject.mapdisplay.model.map.Map;
import com.aceitunaproject.mapdisplay.model.map.Position;
import com.aceitunaproject.mapdisplay.model.map.tile.Tile;
import com.aceitunaproject.shared.gamelement.GameElement;
import com.aceitunaproject.shared.gamelement.Installation;
import com.aceitunaproject.shared.gamelement.Settlement;
import com.aceitunaproject.shared.map.MapTile;
import com.aceitunaproject.shared.message.concrete.*;

//graphics imports
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import java.security.InvalidParameterException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

/**
 * Aceituna Client Main Class
 * 
 * @author Federico Bergstein
 * 
 */
public class AceitunaClient extends JFrame implements ActionListener,
		AceitunaAllEventsHandler, AceitunaConnectionEventsHandler,
		MouseListener {

	/**
	 * Obligatory serial version id...
	 */
	private static final long serialVersionUID = 1L;

	/** The input field for the user to enter a chat message. */
	protected final JTextField inputField = new JTextField();

	/** The panel that wraps the text input field */
	protected final JPanel inputPanel = new JPanel();

	/** the panel that wraps the terrain and unit data. */
	protected final JPanel detailsPanel = new JPanel();

	/** The status indicator. */
	protected final JLabel statusLabel = new JLabel();

	/** the map panel */
	protected MapPane mapPanel = new MapPane();

	/** the jlist that displays a list of units/installations/settlements */
	protected JList list;

	/** the jlist that displays a list of orders */
	protected JList ordersList;

	/** the data model for the unit list */
	protected ElementListModel elementListModel;

	/** the data model for the orders list */
	protected OrdersListModel ordersListModel;

	/** the label with the terrain info */
	protected JLabel terrainLabel;

	/** the last received map (the map that is currently on screen) */
	protected List<MapTile> lastMap;

	/** id of the selected unit */
	protected Long selectedUnitID;

	/** the interface with the aceituna server */
	protected AceitunaClientInterface clientInterface;

	/** the map drawer class to display the map graphics */
	protected MapDrawer mapDrawer;

	/** the logged in flag */
	protected boolean isLoggedIn = false;

	/** the orders map */
	private java.util.Map<EnumParameterType, List<Order>> orders;

	/** the current order being constructed */
	private Order currentOrder;

	public void ChatMessageReceived(ChatMessageReceivedEvent message) {
		// TODO Auto-generated method stub

	}

	public void MapAreaRefresh(MapAreaRefreshEvent message) {

		drawMap(message);
		lastMap = message.getAreaTiles();
	}

	/**
	 * Runs an instance of this client.
	 * 
	 * @param args
	 *            the command-line arguments (unused)
	 */
	public static void main(String[] args) {
		new AceitunaClient();
	}

	/**
	 * Creates a new client UI.
	 */
	public AceitunaClient() {
		this("Aceituna Client");
	}

	/**
	 * Creates a new client UI with the given window title.
	 * 
	 * @param title
	 *            the title for the client's window
	 */
	protected AceitunaClient(String title) {
		super(title);
		// set up
		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (UnsupportedLookAndFeelException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		Container c = getContentPane();
		c.setLayout(new BorderLayout());

		// Setups lists and panels
		setupElementList();
		setupOrdersList();
		setupDetailsPanel();
		setupInputPanel();
		setupStatusLabel();

		// sets itself as the mouse listener for the map
		mapPanel.addMouseListener(this);

		// adds panels to container
		c.add(detailsPanel, BorderLayout.EAST);
		c.add(mapPanel, BorderLayout.CENTER);
		c.add(statusLabel, BorderLayout.NORTH);
		c.add(inputPanel, BorderLayout.SOUTH);

		// set up the window properties

		setExtendedState(MAXIMIZED_BOTH);
		setDefaultCloseOperation(DISPOSE_ON_CLOSE);
		setVisible(true);

	}

	private void setupStatusLabel() {

		statusLabel.setFocusable(false);
		setStatus("Not Started");

	}

	private void setupInputPanel() {

		inputField.addActionListener(this);
		inputPanel.setLayout(new BorderLayout());
		inputPanel.add(inputField, BorderLayout.SOUTH);
		inputPanel.setEnabled(false);

	}

	private void setupDetailsPanel() {
		terrainLabel = new JLabel();
		detailsPanel.setLayout(new BorderLayout());
		detailsPanel.add(terrainLabel, BorderLayout.NORTH);
		detailsPanel.add(list, BorderLayout.CENTER);
		detailsPanel.add(ordersList, BorderLayout.SOUTH);

	}

	/***
	 * Setups the orders list.
	 */
	private void setupOrdersList() {
		ordersListModel = new OrdersListModel();
		ordersList = new JList(ordersListModel);
		ordersList.setName("ordersList");
		ordersList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		ordersList.setSelectedIndex(0);
		addOrdersListEventHandlers();
		ordersList.setVisibleRowCount(5);
		ordersList.setModel(ordersListModel);

	}

	/***
	 * Setups the entities (Unit, Settlement, Installation) list.
	 */
	private void setupElementList() {
		elementListModel = new ElementListModel();
		list = new JList(elementListModel);
		list.setName("elementList");
		list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		list.setSelectedIndex(0);
		addElementListEventHandlers();

		list.setVisibleRowCount(5);

	}

	/**
	 * Creates the orders map, assigning possible orders for each type of
	 * entity.
	 */
	private void createOrdersMap() {

		orders = new HashMap<EnumParameterType, List<Order>>();

		List<Order> unitOrders = new LinkedList<Order>();
		List<Order> tileOrders = new LinkedList<Order>();
		List<Order> installationOrders = new LinkedList<Order>();
		List<Order> settlementOrders = new LinkedList<Order>();

		unitOrders.add(new MoveUnit(clientInterface));
		unitOrders.add(new BuildSettlement(clientInterface));
		unitOrders.add(new BuildInstallation(clientInterface));
		settlementOrders.add(new RecruitUnit(clientInterface));
		
		orders.put(EnumParameterType.Unit, unitOrders);
		orders.put(EnumParameterType.Tile, tileOrders);
		orders.put(EnumParameterType.Installation, installationOrders);
		orders.put(EnumParameterType.Settlement, settlementOrders);

	}

	/**
	 * Initiates asynchronous login to the SGS server specified by the host and
	 * port properties.
	 */
	protected void login(String userName) {

		clientInterface = new AceitunaClientInterfaceImpl(userName);

		// create the orders Map with the interface
		createOrdersMap();

		clientInterface.registerToEvent((MapAreaRefreshHandler) this);
		clientInterface.registerToEvent((ChatMessageReceivedHandler) this);
		clientInterface
				.registerToEvent((RuleViolationEventReceivedHandler) this);
		clientInterface.registerForConnectionEvents(this);
		clientInterface.login();

	}

	/**
	 * Displays the given string in this client's status bar.
	 * 
	 * @param status
	 *            the status message to set
	 */
	protected void setStatus(String status) {

		statusLabel.setText("Status: " + status);
	}

	/**
	 * Returns the user-supplied text from the input field, and clears the field
	 * to prepare for more input.
	 * 
	 * @return the user-supplied text from the input field
	 */
	protected String getInputText() {
		try {
			return inputField.getText();
		} finally {
			inputField.setText("");
		}
	}

	public void loggedIn() {
		inputPanel.setEnabled(true);
		setStatus("Logged in");
		this.setTitle("Aceituna Client - Logged In as : "
				+ clientInterface.getPlayer());
		
		requestMapRefresh();
		isLoggedIn = true;
	}

	public void loginFailed(String reason) {
		setStatus("Login failed: " + reason);
	}

	public void disconnected(boolean graceful, String reason) {
		inputPanel.setEnabled(false);
		setStatus("Disconnected: " + reason);
	}

	public void reconnected() {
		setStatus("reconnected");
	}

	public void reconnecting() {
		setStatus("reconnecting");
	}

	// Implement ActionListener

	/**
	 * {@inheritDoc}
	 * <p>
	 * Parses the user request and sends the appropriate order to the server
	 */
	public void actionPerformed(ActionEvent event) {

		String text = getInputText();

		if (text.contains("login")) {
			System.out.println("login in");
			login(text.substring(6, text.length()));
		} else {
			if (text.contains("move")) {
				Long id = Long.valueOf(text.substring(5, 7));
				int newX = Integer.valueOf(text.substring(8, 9));
				int newY = Integer.valueOf(text.substring(10, 11));

				// setStatus("id=" + id + " x= " + newX + " y= " + newY);

				clientInterface.moveUnit(id, newX, newY);
				requestMapRefresh();// asks for a map refresh
			}

			else {
				if (text.contains("refresh")) {
					requestMapRefresh();// asks for a map refresh;
				} else {
					setStatus("unrecognized command");
				}
			}
		}

	}

	/**
	 * requests a map refresh from the server.
	 */
	private void requestMapRefresh() {

		// Since immediate refresh after login caused exceptions in slow
		// machines/connections.
		// if its not logged in waits a few seconds to let the server finish the
		// login
		if (!isLoggedIn) {
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		// sends the message
		clientInterface.requestMapRefresh();

	}

	/**
	 * uses the map drawer to show the map sent by the server.
	 * 
	 * @param message
	 *            the map area refresh event message received.
	 */
	public void drawMap(MapAreaRefreshEvent message) {

		mapDrawer = new MapDrawerImpl(mapPanel, translateMap(message),
				new Dimension(100, 100));
		mapPanel.setMapDrawer(mapDrawer);
		mapDrawer.setFocus(0, 0);
		mapDrawer.setDisplayGrid(false);
		mapPanel.repaint();
		showTileDetails(null);
		ordersListModel = new OrdersListModel();
		ordersList.setModel(ordersListModel);
	}

	private Map translateMap(MapAreaRefreshEvent message) {

		List<MapTile> receivedTiles = message.getAreaTiles();
		int n = 0;

		Tile[][] tile = new Tile[7][15];
		for (int i = 0; i < tile.length; i++) {
			for (int j = 0; j < tile[i].length; j++) {
				tile[i][j] = createTile(i, j, "");

				n++;
			}
		}

		for (MapTile mapTile : receivedTiles) {

			tile[mapTile.getPosx()][mapTile.getPosy()].setBasicTileType(mapTile
					.getTerrain().get(0).getName());

			Settlement theSettlement = mapTile.getSettlement();
			if (theSettlement != null) {
				System.out.println("settlement!");
				Thing t = new Thing("Settlement");
				tile[mapTile.getPosx()][mapTile.getPosy()].addThing(t);
			}

			for (int i = 0; i < mapTile.getUnits().size(); i++) {

				Thing t = new Thing("veteranSoldier");
				tile[mapTile.getPosx()][mapTile.getPosy()].addThing(t);
			}

			for (Installation installation : mapTile.getInstallations()) {
				System.out.println("Installation!");
				String instType = "";
				if (installation.isComplete()) {
					instType = "installation_" + installation.getType();
				} else {
					instType = "installation_Incomplete";
				}
				Thing t = new Thing(instType);
				tile[mapTile.getPosx()][mapTile.getPosy()].addThing(t);
			}

		}

		Map translatedMap = new Map(tile);
		return translatedMap;

	}

	private Tile createTile(int x, int y, String name) {

		return new Tile(name, x, y);

	}

	public void RuleViolationEventReceived(RuleViolationEvent message) {
		setStatus(message.getRuleViolations().getReasons().get(0).toString());
	}

	public void mouseClicked(MouseEvent arg0) {

		Position p = mapDrawer
				.convertToMapCoordinates(arg0.getX(), arg0.getY());
		if (arg0.getButton() != MouseEvent.BUTTON1) {
			moveSelectedUnit(p);

		} else {
			selectTile(p);
		}

	}

	private void selectTile(Position p) {

		MapTile theTile = getTile(p.x, p.y);

		mapDrawer.highlightTile(theTile.getPosx(), theTile.getPosy());
		mapPanel.repaint();

		showTileDetails(theTile);
		showPossibleOrders(EnumParameterType.Tile);
		manageOrderEvent(theTile, EnumParameterType.Tile);
	}

	@SuppressWarnings("unchecked")
	private <E, T extends BasicParameter<E>> void manageOrderEvent(E value,
			EnumParameterType type) {

		if (currentOrder != null
				&& currentOrder.getParameter().getParameterType() == type) {

			T theParameter = (T) currentOrder.getParameter();

			theParameter.setValue(value);

			if (!currentOrder.moveToNextParameter()) {

				currentOrder.execute();
				currentOrder = null;
				// elementListModel = new ElementListModel();
				// ordersListModel = new OrdersListModel();
				// list.setModel(elementListModel);
				// ordersList.setModel(ordersListModel);
			}
		}

		displayCurrentOrder();

	}

	private void showPossibleOrders(EnumParameterType type) {

		if (currentOrder == null || currentOrder.hasExecuted()) {
			currentOrder = null;
			ordersListModel = new OrdersListModel();
			for (Order order : orders.get(type)) {
				ordersListModel.addOrder(order);
			}

			ordersList.setModel(ordersListModel);
		}

	}

	private void showTileDetails(MapTile theTile) {
		elementListModel = new ElementListModel();
		if (theTile == null) {
			terrainLabel.setText("");
		} else {
			terrainLabel.setText("Tile " + theTile.getPosx() + ","
					+ theTile.getPosy() + ". Terrain Type = "
					+ theTile.getTerrain().get(0).getName());

			for (GameElement e : theTile.getUnits()) {

				elementListModel.add(new ElementWrapper(e,
						EnumGameElementType.Unit));
			}

			for (GameElement e : theTile.getInstallations()) {

				elementListModel.add(new ElementWrapper(e,
						EnumGameElementType.Installation));
			}

			if (theTile.getSettlement() != null) {
				elementListModel
						.add(new ElementWrapper(theTile.getSettlement(),
								EnumGameElementType.Settlement));
			}

		}
		list.setModel(elementListModel);
	}

	private void moveSelectedUnit(Position p) {
		if (selectedUnitID != null) {
			clientInterface.moveUnit(selectedUnitID, p.x, p.y);
			clientInterface.requestMapRefresh();
			setStatus("moved unit");
		}

	}

	private MapTile getTile(int x, int y) {
		for (MapTile tile : lastMap) {
			if (tile.getPosx() == x && tile.getPosy() == y) {
				return tile;
			}
		}
		return null;
	}

	public void mouseEntered(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	public void mouseExited(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	public void mousePressed(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	public void mouseReleased(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	public void addElementListEventHandlers() {
		list.addListSelectionListener(new ListSelectionListener() {

			public void valueChanged(ListSelectionEvent e) {
				elementListValueChanged(e);

			}
		});
		// list.addListSelectionListener(this);

	}

	private void elementListValueChanged(ListSelectionEvent e) {

		if (elementListModel != null && elementListModel.size() > 0) {

			ElementWrapper selectedElement = elementListModel.getElement(list
					.getSelectedIndex());

			setStatus(selectedElement.getType().toString() + " Selected ID = "
					+ selectedElement.getId());

			EnumParameterType type;

			switch (selectedElement.getType()) {
			case Unit:

				selectedUnitID = selectedElement.getId();
				type = EnumParameterType.Unit;
				manageOrderEvent(selectedElement.getAsUnit(),
						EnumParameterType.Unit);

				break;

			case Installation:

				type = EnumParameterType.Installation;
				manageOrderEvent(selectedElement.getAsInstallation(), type);

				break;
			case Settlement:

				type = EnumParameterType.Settlement;
				manageOrderEvent(selectedElement.getAsSettlement(), type);

				break;

			default:
				throw new InvalidParameterException();
			}

			showPossibleOrders(type);
		}

	}

	public void addOrdersListEventHandlers() {

		System.out.println("handler");
		ordersList.addListSelectionListener(new ListSelectionListener() {

			public void valueChanged(ListSelectionEvent e) {
				System.out.println("handlerFired");
				orderListsValueChanged(e);

			}

		});
		// ordersList.addListSelectionListener(this);
	}

	private void orderListsValueChanged(ListSelectionEvent e) {

		if (ordersListModel != null && ordersListModel.size() > 0) {

			if (currentOrder == null
					|| currentOrder.getName() != ordersListModel.getOrder(
							ordersList.getSelectedIndex()).getName()) {

				System.out.println(((JList) e.getSource()).getName());
				System.out.println("Order selected "
						+ ordersListModel.getOrder(
								ordersList.getSelectedIndex()).getName());

				setStatus("Order selected "
						+ ordersListModel.getOrder(
								ordersList.getSelectedIndex()).getName());
				currentOrder = ordersListModel.getOrder(ordersList
						.getSelectedIndex());

				ElementWrapper selectedElement = elementListModel
						.getElement(list.getSelectedIndex());

				switch (selectedElement.getType()) {

				case Unit:
					((UnitParameter) currentOrder.getParameter())
							.setValue(selectedElement.getAsUnit());
					break;

				case Installation:
					((InstallationParameter) currentOrder.getParameter())
							.setValue(selectedElement.getAsInstallation());
					break;

				case Settlement:
					((SettlementParameter) currentOrder.getParameter())
							.setValue(selectedElement.getAsSettlement());
					break;

				default:
					break;
				}

				if (!currentOrder.moveToNextParameter())

				{
					if (!currentOrder.hasExecuted()) {
						currentOrder.execute();
						currentOrder = null;
					}
				}
			}

		}
	}

	// 
	// public void valueChanged(ListSelectionEvent arg0) {
	//
	// displayCurrentOrder();
	// }
	//
	private void displayCurrentOrder() {

		if (currentOrder != null) {
			setStatus(currentOrder.getDescription());
		}
	}

}
